#include <base\dpViewVolume.h>
#include <base\viewVolume.h>
#include <base\dpmatrix.h>

vec3d operator + (const vec3d & v1, const vec3d & v2) {
  vec3d v = v1;
  v =v + v2;
  return v;
}

vec3d operator - (const vec3d & v1, const vec3d & v2) {
  vec3d v = v1;
  v = v - v2;
  return v;
}

void
dpViewVolume::copyValues(viewVolume & vv) {
  vv.projPoint = dp_to_vec3f(this->projPoint);
  vv.projDir = dp_to_vec3f(this->projDir);
  vv.nearDist = static_cast<float>(this->nearDist);
  vv.nearToFar = static_cast<float>(this->nearToFar);
  vv.llf = dp_to_vec3f(this->llf + this->projPoint);
  vv.lrf = dp_to_vec3f(this->lrf + this->projPoint);
  vv.ulf = dp_to_vec3f(this->ulf + this->projPoint);
}

void 
dpViewVolume::perspective(double fovy, double aspect, double nearval, double farval) {
  this->projPoint = vec3f(0.0f, 0.0f, 0.0f);
  this->projDir = vec3f(0.0f, 0.0f, -1.0f);
  this->nearDist = nearval;
  this->nearToFar = farval - nearval;

  double top = nearval * double(tan(fovy/2.0f));
  double bottom = -top;
  double left = bottom * aspect;
  double right = -left;

  this->llf = vec3d(left, bottom, -nearval);
  this->lrf = vec3d(right, bottom, -nearval);
  this->ulf = vec3d(left, top, -nearval);
}

void
dpViewVolume::translateCamera(const vec3d & v) {
  projPoint = projPoint + v;
}

void
dpViewVolume::rotateCamera(const dprotation& q) {
  dpmatrix mat;
  mat.setRotate(q);

  mat.multDirMatrix(this->projDir, this->projDir);
  mat.multDirMatrix(this->llf, this->llf);
  mat.multDirMatrix(this->lrf, this->lrf);
  mat.multDirMatrix(this->ulf, this->ulf);
}

vec3f 
dpViewVolume::dp_to_vec3f(const vec3d & v) {
  return vec3f(static_cast<float>(v.x), static_cast<float>(v.y), static_cast<float>(v.z));
}

void 
dpViewVolume::getPlaneRectangle(const double distance, vec3d & lowerleft,
                                vec3d & lowerright,
                                vec3d & upperleft,
                                vec3d & upperright) const {
  vec3d near_ur = (vec3d)this->ulf + ((vec3d)this->lrf - (vec3d)this->llf);

  double depth = this->nearDist + distance;
  vec3d dir;
  dir = this->llf;
  (void) dir.normalize();
  lowerleft = (dir * depth) / dir.dot(this->projDir);

  dir = this->lrf;
  dir.normalize(); // safe to normalize here
  lowerright = dir * depth / dir.dot(this->projDir);

  dir = this->ulf;
  (void) dir.normalize(); // safe to normalize here
  upperleft = dir * depth / dir.dot(this->projDir);
  
  dir = near_ur;
  (void) dir.normalize(); // safe to normalize here
  upperright = dir * depth / dir.dot(this->projDir);
}

void 
dpViewVolume::getViewVolumePlanes(plane planes[6]) const {
  vec3d far_ll;
  vec3d far_lr;
  vec3d far_ul;
  vec3d far_ur;

  this->getPlaneRectangle(this->nearToFar, far_ll, far_lr, far_ul, far_ur);
  vec3d near_ur = this->ulf + (this->lrf-this->llf);

  vec3f f_ulf = dp_to_vec3f(this->ulf + this->projPoint);
  vec3f f_llf = dp_to_vec3f(this->llf + this->projPoint);
  vec3f f_lrf = dp_to_vec3f(this->lrf + this->projPoint);
  vec3f f_near_ur = dp_to_vec3f(near_ur + this->projPoint);
  vec3f f_far_ll = dp_to_vec3f(far_ll + this->projPoint);
  vec3f f_far_lr = dp_to_vec3f(far_lr + this->projPoint);
  vec3f f_far_ul = dp_to_vec3f(far_ul + this->projPoint);
  vec3f f_far_ur = dp_to_vec3f(far_ur + this->projPoint);

  planes[0] = plane(f_ulf, f_llf, f_far_ll);  // left
  planes[1] = plane(f_llf, f_lrf, f_far_lr); // bottom
  planes[2] = plane(f_lrf, f_near_ur, f_far_ur); // right
  planes[3] = plane(f_near_ur, f_ulf, f_far_ul); // top
  planes[4] = plane(f_ulf, f_near_ur, f_lrf); // near
  planes[5] = plane(f_far_ll, f_far_lr, f_far_ur); // far

  // check for inverted view volume (negative aspectRatio)
  if (!planes[0].isInHalfSpace(f_lrf)) {
    vec3f n;
    float D;

    n = planes[0].getNormal();
    D = planes[0].getDistanceFromOrigin();    
    planes[0] = plane(-n, -D);

    n = planes[2].getNormal();
    D = planes[2].getDistanceFromOrigin();    
    planes[2] = plane(-n, -D);
  }
  if (!planes[1].isInHalfSpace(f_near_ur)) {
    vec3f n;
    float D;

    n = planes[1].getNormal();
    D = planes[1].getDistanceFromOrigin();    
    planes[1] = plane(-n, -D);

    n = planes[3].getNormal();
    D = planes[3].getDistanceFromOrigin();    
    planes[3] = plane(-n, -D);
  
  }

  if (!planes[4].isInHalfSpace(f_far_ll)) {
    vec3f n;
    float D;

    n = planes[4].getNormal();
    D = planes[4].getDistanceFromOrigin();    
    planes[4] = plane(-n, -D);

    n = planes[5].getNormal();
    D = planes[5].getDistanceFromOrigin();    
    planes[5] = plane(-n, -D);
  }
}
